package downstream

import (
	"context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/status"
	"mall/api/pay"
	"mall/internal/conf"
	"mall/internal/constant"
	"mall/internal/model"
	"mall/pkg/ecode"
	"mall/pkg/log"
	"mall/pkg/network"
	"mall/pkg/request_key"
	"strconv"
	"time"
)

func newPayClient(ctx context.Context, addr string) (
	client pay.PayClient, err error) {
	var conn *grpc.ClientConn
	conn, err = grpc.Dial(addr, grpc.WithInsecure(),
		grpc.WithUnaryInterceptor(network.ClientInterceptor(
			"bilin_pay", time.Second*3, payErrorTranslator)))
	if err != nil {
		err = ecode.AppendMessage(constant.ErrRemoteError, "支付中心服务地址无法建立有效连接, err=%s", err.Error())
		log.FromContext(ctx).Warnw(err.Error())
		return
	}
	client = pay.NewPayClient(conn)
	return
}

func payErrorTranslator(err error) (errCode *ecode.ErrorCode) {
	rpcStatus := status.Convert(err)
	for _, detail := range rpcStatus.Details() {
		switch t := detail.(type) {
		case *pay.DefaultResp:
			errCode = ecode.NewError(t.ErrorCode, t.ErrorMessage, ecode.ErrorType{
				GRPCCode: rpcStatus.Code(),
				ErrType:  t.ErrorType,
			})
			return
		}
	}
	errCode = ecode.AppendMessage(
		constant.ErrRemoteError, "支付服务系统错误，请稍后再试")
	return
}

func (d *Downstream) GetAvailablePayTypes(ctx context.Context, sgId, payStoreId uint32,
	sourceType pay.SourceType) (payTypeMap map[pay.PayType]string, err error) {
	if d.payClient == nil {
		if d.payClient, err = newPayClient(
			ctx, conf.Conf.Downstream.PayAddress); err != nil {
			return
		}
	}
	var resp *pay.PayTypeList
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*3)
	defer cancel()
	if resp, err = d.payClient.GetAvailablePayTypes(clientCtx, &pay.GetAvailablePayTypesRequest{
		SgId:       sgId,
		Source:     sourceType,
		BizType:    pay.BizType_BIZ_MALL,
		PayStoreId: payStoreId,
	}); err != nil {
		return
	}
	payTypeMap = make(map[pay.PayType]string)
	for _, payType := range resp.PayTypes {
		payTypeMap[payType.Id] = payType.Name
	}
	return
}

func (d *Downstream) CreatePaymentOrder(ctx context.Context, request *model.OrderRequest,
	appID, openID string, payStoreId uint32, sourceType pay.SourceType, payType pay.PayType,
	cardInfo []byte) (resp *pay.DoPayOrderView, err error) {
	if d.payClient == nil {
		if d.payClient, err = newPayClient(
			ctx, conf.Conf.Downstream.PayAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*3)
	defer cancel()
	if resp, err = d.payClient.CreatePayOrder(clientCtx, &pay.CreatePayOrderRequest{
		OrderKey:   strconv.Itoa(int(request.MemberId)),
		SgId:       request.SgId,
		AppId:      appID,
		OpenId:     openID,
		SourceType: sourceType,
		BizType:    pay.BizType_BIZ_MALL,
		BizStore:   request.PStoreId, // parker use request.PStoreId=>payStoreId
		BizNo:      request.BizNo,
		PayType:    payType,
		Amount:     request.Trans,
		Submitted:  true,
		Remark:     constant.EmptyBytes,
		IsvAppId:   "",
		IsvOpenId:  "",
		Details:    nil,
		CardInfo:   cardInfo,
		PayStoreId: payStoreId,
	}); err != nil {
		return
	}
	return
}

func (d *Downstream) TriggerPayment(ctx context.Context, payOrderNumber string) (
	resp *pay.PayOrderView, err error) {
	if d.payClient == nil {
		if d.payClient, err = newPayClient(
			ctx, conf.Conf.Downstream.PayAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*3)
	defer cancel()
	if resp, err = d.payClient.TriggerPaySuccess(clientCtx, &pay.CommonOrderRequest{
		OrderNumber: payOrderNumber,
	}); err != nil {
		return
	}
	return
}

func (d *Downstream) QueryPayment(ctx context.Context, payOrderNumber string) (
	resp *pay.PayOrderView, err error) {
	if d.payClient == nil {
		if d.payClient, err = newPayClient(
			ctx, conf.Conf.Downstream.PayAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*3)
	defer cancel()
	if resp, err = d.payClient.GetPayOrderView(clientCtx, &pay.CommonOrderRequest{
		OrderNumber: payOrderNumber,
	}); err != nil {
		return
	}
	return
}

func (d *Downstream) CancelPayment(ctx context.Context, payOrderNumber string) (err error) {
	if d.payClient == nil {
		if d.payClient, err = newPayClient(
			ctx, conf.Conf.Downstream.PayAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*3)
	defer cancel()
	if _, err = d.payClient.CancelPayOrder(clientCtx, &pay.CommonOrderRequest{
		OrderNumber: payOrderNumber,
	}); err != nil {
		return
	}
	return
}

func (d *Downstream) CreateRefundPaymentOrder(ctx context.Context, req *pay.CreateRefundOrderRequest) (resp *pay.RefundOrderView, err error) {
	if d.payClient == nil {
		if d.payClient, err = newPayClient(
			ctx, conf.Conf.Downstream.PayAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*3)
	defer cancel()
	if resp, err = d.payClient.CreateRefundOrder(clientCtx, req); err != nil {
		return
	}
	return
}

func (d *Downstream) RefundRetry(ctx context.Context, payOrderNumber string,
	empId uint32, isMarked bool) (resp *pay.RefundOrderView, err error) {
	if d.payClient == nil {
		if d.payClient, err = newPayClient(
			ctx, conf.Conf.Downstream.PayAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*3)
	defer cancel()
	if resp, err = d.payClient.RefundRetry(clientCtx, &pay.RefundRetryRequest{
		OrderNumber: payOrderNumber,
		EmpId:       empId,
		IsMarked:    isMarked,
	}); err != nil {
		return
	}
	return
}
